/*
 * Copyright 2013 Nicholas Bilyk
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

package jasper;

import jasper.model.elements.Schema;
import jasper.model.elements.SchemaNode;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Task;
import org.apache.tools.ant.types.Path;
import org.w3c.dom.Document;
import org.w3c.dom.Element;

import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.*;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.*;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Vector;

/**
 * An ANT Task to convert classes to xml schema .xsd files.
 *
 * @author nbilyk
 */
public class CodeToSchemaTask extends Task {

	/**
	 * The path at which the generated source will be written. Exposed
	 * to ant as "outputPath" attribute.
	 */
	private Path outputPath;

	/**
	 * The source files from which to generate the schemas.
	 */
	private Vector<Path> targets = new Vector<Path>();

	/**
	 * Verbose logging.
	 */
	private Boolean verbose = false;

	/**
	 * The marshaller map represents which file extension gets mapped to which schema marshaller.
	 */
	private HashMap<String, ISchemaGenerator> marshallerMap = new HashMap<String, ISchemaGenerator>();

	public CodeToSchemaTask() {
		marshallerMap.put("as", new CodeToSchemaAs3());
	}

	//-------------------------------
	// Task properties
	//-------------------------------

	/**
	 * The destination to save the generated schemas.
	 */
	public void setOutputPath(Path value) {
		outputPath = value;
	}

	/**
	 * Adds a set of files from which to generate the schemas.
	 */
	public void addTarget(Path value) {
		targets.add(value);
	}

	/**
	 * Verbose logging. (Default false)
	 */
	public void setVerbose(Boolean value) {
		verbose = value;
	}

	//-------------------------------
	// Execute
	//-------------------------------

	protected void validate() {
		if (outputPath == null) throw new BuildException("outputPath property not set.");
		if (outputPath.list().length != 1) throw new BuildException("outputPath must refer to a single location.");
		if (targets.size() < 1) throw new BuildException("no targets were added.");
	}

	/**
	 * Called by ant and initiates the analysis process.
	 */
	public void execute() {
		validate();
		File outputFolder = new File(outputPath.list()[0]);
		if (outputFolder.exists()) {
			if (!outputFolder.isDirectory()) throw new BuildException("outputPath is not a directory.");
		} else {
			Boolean success = outputFolder.mkdirs();
			if (!success) throw new BuildException("Could not create the directory at " + outputFolder.getPath());
		}

		for (Iterator iPaths = targets.iterator(); iPaths.hasNext(); ) {
			Path path = (Path) iPaths.next();

			for (String filename : path.list()) {
				if (verbose) log("Processing: " + filename);
				File fileToParse = new File(filename);
				FileInputStream fileStream;

				String extension = FilenameUtils.getExtension(filename);
				if (!marshallerMap.containsKey(extension)) {
					throw new BuildException("No marshaller found for the extension \"" + extension + "\"");
				}
				ISchemaGenerator schemaMarshaller = marshallerMap.get(extension);
				try {
					fileStream = new FileInputStream(fileToParse);
				} catch (FileNotFoundException e) {
					log(e, 1);
					continue;
				}

				StringWriter stringWriter = new StringWriter();
				String contents;
				try {
					IOUtils.copy(fileStream, stringWriter, "UTF-8");
					contents = stringWriter.toString();
					fileStream.close();
				} catch (IOException e) {
					throw new BuildException("Failed closing file " + filename);
				}
				if (contents != null) {
					Schema model = schemaMarshaller.generateSchema(contents);
					log("Schema generated for " + fileToParse.getName());
					Document document;
					// create the document used for all schemas
					DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
					documentBuilderFactory.setNamespaceAware(true);
					try {
						document = documentBuilderFactory.newDocumentBuilder().newDocument();
					} catch (ParserConfigurationException e) {
						throw new IllegalStateException("Parser configuration error " +
								e.getMessage());
					}

					Element rootNode = document.createElementNS(SchemaNode.DEFAULT_NAMESPACE, "schema");
					document.appendChild(rootNode);
					model.marshal(document, rootNode);

					String output = documentToString(document);
					log("Out: " + output);

//					try {
//						File outFile = new File("E:/Java/Reference/bin/images/statsTest.txt");
//						FileOutputStream is = new FileOutputStream(statText);
//						OutputStreamWriter osw = new OutputStreamWriter(is);
//						Writer w = new BufferedWriter(osw);
//						w.write("POTATO!!!");
//						w.close();
//					} catch (IOException e) {
//						System.err.println("Problem writing to the file statsTest.txt");
//					}
				}
			}
		}
	}

	/**
	 * Converts the Document XML to a String.
	 *
	 * @param document The XML Document to convert.
	 * @return A pretty String we can write to a file.
	 */
	protected String documentToString(Document document) {
		Transformer transformer;
		try {
			transformer = TransformerFactory.newInstance().newTransformer();
		} catch (TransformerConfigurationException exception) {
			throw new BuildException(exception.getMessage());
		}
		transformer.setOutputProperty(OutputKeys.INDENT, "yes");
		transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4");
		StringWriter writer = new StringWriter();
		try {
			transformer.transform(new DOMSource(document), new StreamResult(writer));
		} catch (TransformerException exception) {
			throw new BuildException(exception.getMessage());
		}
		String output = writer.getBuffer().toString();
		return output;
	}
}
